// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Linq;
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace CommunityToolkit.Mvvm.SourceGenerators.UnitTests;

[TestClass]
public class Test_SourceGeneratorsCodegen
{
    [TestMethod]
    public void ObservablePropertyWithNonNullableReferenceType_EmitsMemberNotNullAttribute()
    {
        string source = """
            using CommunityToolkit.Mvvm.ComponentModel;

            #nullable enable

            namespace MyApp;
            
            partial class MyViewModel : ObservableObject
            {
                [ObservableProperty]
                private string name;
            }
            """;

#if NET6_0_OR_GREATER
        string result = """
            // <auto-generated/>
            #pragma warning disable
            #nullable enable
            namespace MyApp
            {
                /// <inheritdoc/>
                partial class MyViewModel
                {
                    /// <inheritdoc cref="name"/>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
                    public string Name
                    {
                        get => name;
                        [global::System.Diagnostics.CodeAnalysis.MemberNotNull("name")]
                        set
                        {
                            if (!global::System.Collections.Generic.EqualityComparer<string>.Default.Equals(name, value))
                            {
                                OnNameChanging(value);
                                OnNameChanging(default, value);
                                OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Name);
                                name = value;
                                OnNameChanged(value);
                                OnNameChanged(default, value);
                                OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Name);
                            }
                        }
                    }

                    /// <summary>Executes the logic for when <see cref="Name"/> is changing.</summary>
                    /// <param name="value">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="Name"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnNameChanging(string value);
                    /// <summary>Executes the logic for when <see cref="Name"/> is changing.</summary>
                    /// <param name="oldValue">The previous property value that is being replaced.</param>
                    /// <param name="newValue">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="Name"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnNameChanging(string? oldValue, string newValue);
                    /// <summary>Executes the logic for when <see cref="Name"/> just changed.</summary>
                    /// <param name="value">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="Name"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnNameChanged(string value);
                    /// <summary>Executes the logic for when <see cref="Name"/> just changed.</summary>
                    /// <param name="oldValue">The previous property value that was replaced.</param>
                    /// <param name="newValue">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="Name"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnNameChanged(string? oldValue, string newValue);
                }
            }
            """;
#else
        string result = """
            // <auto-generated/>
            #pragma warning disable
            #nullable enable
            namespace MyApp
            {
                /// <inheritdoc/>
                partial class MyViewModel
                {
                    /// <inheritdoc cref="name"/>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
                    public string Name
                    {
                        get => name;
                        set
                        {
                            if (!global::System.Collections.Generic.EqualityComparer<string>.Default.Equals(name, value))
                            {
                                OnNameChanging(value);
                                OnNameChanging(default, value);
                                OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Name);
                                name = value;
                                OnNameChanged(value);
                                OnNameChanged(default, value);
                                OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Name);
                            }
                        }
                    }

                    /// <summary>Executes the logic for when <see cref="Name"/> is changing.</summary>
                    /// <param name="value">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="Name"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnNameChanging(string value);
                    /// <summary>Executes the logic for when <see cref="Name"/> is changing.</summary>
                    /// <param name="oldValue">The previous property value that is being replaced.</param>
                    /// <param name="newValue">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="Name"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnNameChanging(string? oldValue, string newValue);
                    /// <summary>Executes the logic for when <see cref="Name"/> just changed.</summary>
                    /// <param name="value">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="Name"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnNameChanged(string value);
                    /// <summary>Executes the logic for when <see cref="Name"/> just changed.</summary>
                    /// <param name="oldValue">The previous property value that was replaced.</param>
                    /// <param name="newValue">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="Name"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnNameChanged(string? oldValue, string newValue);
                }
            }
            """;
#endif

        VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result));
    }

    [TestMethod]
    public void ObservablePropertyWithNonNullableValueType_DoesNotEmitMemberNotNullAttribute()
    {
        string source = """
            using System;
            using CommunityToolkit.Mvvm.ComponentModel;

            #nullable enable

            namespace MyApp;
            
            partial class MyViewModel : ObservableObject
            {
                [ObservableProperty]
                private Guid id;
            }
            """;

        string result = """
            // <auto-generated/>
            #pragma warning disable
            #nullable enable
            namespace MyApp
            {
                /// <inheritdoc/>
                partial class MyViewModel
                {
                    /// <inheritdoc cref="id"/>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
                    public global::System.Guid Id
                    {
                        get => id;
                        set
                        {
                            if (!global::System.Collections.Generic.EqualityComparer<global::System.Guid>.Default.Equals(id, value))
                            {
                                OnIdChanging(value);
                                OnIdChanging(default, value);
                                OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Id);
                                id = value;
                                OnIdChanged(value);
                                OnIdChanged(default, value);
                                OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Id);
                            }
                        }
                    }

                    /// <summary>Executes the logic for when <see cref="Id"/> is changing.</summary>
                    /// <param name="value">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="Id"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnIdChanging(global::System.Guid value);
                    /// <summary>Executes the logic for when <see cref="Id"/> is changing.</summary>
                    /// <param name="oldValue">The previous property value that is being replaced.</param>
                    /// <param name="newValue">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="Id"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnIdChanging(global::System.Guid oldValue, global::System.Guid newValue);
                    /// <summary>Executes the logic for when <see cref="Id"/> just changed.</summary>
                    /// <param name="value">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="Id"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnIdChanged(global::System.Guid value);
                    /// <summary>Executes the logic for when <see cref="Id"/> just changed.</summary>
                    /// <param name="oldValue">The previous property value that was replaced.</param>
                    /// <param name="newValue">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="Id"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnIdChanged(global::System.Guid oldValue, global::System.Guid newValue);
                }
            }
            """;

        VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result));
    }

    [TestMethod]
    public void ObservablePropertyWithNonNullableUnconstrainedGenericType_EmitsMemberNotNullAttribute()
    {
        string source = """
            using System;
            using CommunityToolkit.Mvvm.ComponentModel;

            #nullable enable

            namespace MyApp;
            
            partial class MyViewModel<T> : ObservableObject
            {
                [ObservableProperty]
                private T content;
            }
            """;

#if NET6_0_OR_GREATER
        string result = """
            // <auto-generated/>
            #pragma warning disable
            #nullable enable
            namespace MyApp
            {
                /// <inheritdoc/>
                partial class MyViewModel<T>
                {
                    /// <inheritdoc cref="content"/>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
                    public T Content
                    {
                        get => content;
                        [global::System.Diagnostics.CodeAnalysis.MemberNotNull("content")]
                        set
                        {
                            if (!global::System.Collections.Generic.EqualityComparer<T>.Default.Equals(content, value))
                            {
                                OnContentChanging(value);
                                OnContentChanging(default, value);
                                OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Content);
                                content = value;
                                OnContentChanged(value);
                                OnContentChanged(default, value);
                                OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Content);
                            }
                        }
                    }

                    /// <summary>Executes the logic for when <see cref="Content"/> is changing.</summary>
                    /// <param name="value">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="Content"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnContentChanging(T value);
                    /// <summary>Executes the logic for when <see cref="Content"/> is changing.</summary>
                    /// <param name="oldValue">The previous property value that is being replaced.</param>
                    /// <param name="newValue">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="Content"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnContentChanging(T? oldValue, T newValue);
                    /// <summary>Executes the logic for when <see cref="Content"/> just changed.</summary>
                    /// <param name="value">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="Content"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnContentChanged(T value);
                    /// <summary>Executes the logic for when <see cref="Content"/> just changed.</summary>
                    /// <param name="oldValue">The previous property value that was replaced.</param>
                    /// <param name="newValue">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="Content"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnContentChanged(T? oldValue, T newValue);
                }
            }
            """;
#else
        string result = """
            // <auto-generated/>
            #pragma warning disable
            #nullable enable
            namespace MyApp
            {
                /// <inheritdoc/>
                partial class MyViewModel<T>
                {
                    /// <inheritdoc cref="content"/>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
                    public T Content
                    {
                        get => content;
                        set
                        {
                            if (!global::System.Collections.Generic.EqualityComparer<T>.Default.Equals(content, value))
                            {
                                OnContentChanging(value);
                                OnContentChanging(default, value);
                                OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Content);
                                content = value;
                                OnContentChanged(value);
                                OnContentChanged(default, value);
                                OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Content);
                            }
                        }
                    }

                    /// <summary>Executes the logic for when <see cref="Content"/> is changing.</summary>
                    /// <param name="value">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="Content"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnContentChanging(T value);
                    /// <summary>Executes the logic for when <see cref="Content"/> is changing.</summary>
                    /// <param name="oldValue">The previous property value that is being replaced.</param>
                    /// <param name="newValue">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="Content"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnContentChanging(T? oldValue, T newValue);
                    /// <summary>Executes the logic for when <see cref="Content"/> just changed.</summary>
                    /// <param name="value">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="Content"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnContentChanged(T value);
                    /// <summary>Executes the logic for when <see cref="Content"/> just changed.</summary>
                    /// <param name="oldValue">The previous property value that was replaced.</param>
                    /// <param name="newValue">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="Content"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnContentChanged(T? oldValue, T newValue);
                }
            }
            """;
#endif

        VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel`1.g.cs", result));

        // Also test with C# 8 to double check that using a nullable unconstrained type parameter doesn't cause issues, but just a (suppressed) warning
        VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, LanguageVersion.CSharp8, ("MyApp.MyViewModel`1.g.cs", result));
    }

    [TestMethod]
    public void ObservablePropertyWithNonNullableConstrainedNotNullGenericType_EmitsMemberNotNullAttribute()
    {
        string source = """
            using System;
            using CommunityToolkit.Mvvm.ComponentModel;

            #nullable enable

            namespace MyApp;
            
            partial class MyViewModel<T> : ObservableObject
                where T : notnull
            {
                [ObservableProperty]
                private T content;
            }
            """;

#if NET6_0_OR_GREATER
        string result = """
            // <auto-generated/>
            #pragma warning disable
            #nullable enable
            namespace MyApp
            {
                /// <inheritdoc/>
                partial class MyViewModel<T>
                {
                    /// <inheritdoc cref="content"/>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
                    public T Content
                    {
                        get => content;
                        [global::System.Diagnostics.CodeAnalysis.MemberNotNull("content")]
                        set
                        {
                            if (!global::System.Collections.Generic.EqualityComparer<T>.Default.Equals(content, value))
                            {
                                OnContentChanging(value);
                                OnContentChanging(default, value);
                                OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Content);
                                content = value;
                                OnContentChanged(value);
                                OnContentChanged(default, value);
                                OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Content);
                            }
                        }
                    }

                    /// <summary>Executes the logic for when <see cref="Content"/> is changing.</summary>
                    /// <param name="value">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="Content"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnContentChanging(T value);
                    /// <summary>Executes the logic for when <see cref="Content"/> is changing.</summary>
                    /// <param name="oldValue">The previous property value that is being replaced.</param>
                    /// <param name="newValue">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="Content"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnContentChanging(T? oldValue, T newValue);
                    /// <summary>Executes the logic for when <see cref="Content"/> just changed.</summary>
                    /// <param name="value">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="Content"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnContentChanged(T value);
                    /// <summary>Executes the logic for when <see cref="Content"/> just changed.</summary>
                    /// <param name="oldValue">The previous property value that was replaced.</param>
                    /// <param name="newValue">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="Content"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnContentChanged(T? oldValue, T newValue);
                }
            }
            """;
#else
        string result = """
            // <auto-generated/>
            #pragma warning disable
            #nullable enable
            namespace MyApp
            {
                /// <inheritdoc/>
                partial class MyViewModel<T>
                {
                    /// <inheritdoc cref="content"/>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
                    public T Content
                    {
                        get => content;
                        set
                        {
                            if (!global::System.Collections.Generic.EqualityComparer<T>.Default.Equals(content, value))
                            {
                                OnContentChanging(value);
                                OnContentChanging(default, value);
                                OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Content);
                                content = value;
                                OnContentChanged(value);
                                OnContentChanged(default, value);
                                OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Content);
                            }
                        }
                    }

                    /// <summary>Executes the logic for when <see cref="Content"/> is changing.</summary>
                    /// <param name="value">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="Content"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnContentChanging(T value);
                    /// <summary>Executes the logic for when <see cref="Content"/> is changing.</summary>
                    /// <param name="oldValue">The previous property value that is being replaced.</param>
                    /// <param name="newValue">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="Content"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnContentChanging(T? oldValue, T newValue);
                    /// <summary>Executes the logic for when <see cref="Content"/> just changed.</summary>
                    /// <param name="value">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="Content"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnContentChanged(T value);
                    /// <summary>Executes the logic for when <see cref="Content"/> just changed.</summary>
                    /// <param name="oldValue">The previous property value that was replaced.</param>
                    /// <param name="newValue">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="Content"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnContentChanged(T? oldValue, T newValue);
                }
            }
            """;
#endif

        VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel`1.g.cs", result));
    }

    [TestMethod]
    public void ObservablePropertyWithNonNullableConstrainedReferenceTypeGenericType_EmitsMemberNotNullAttribute()
    {
        string source = """
            using System;
            using CommunityToolkit.Mvvm.ComponentModel;

            #nullable enable

            namespace MyApp;
            
            partial class MyViewModel<T> : ObservableObject
                where T : class
            {
                [ObservableProperty]
                private T content;
            }
            """;

#if NET6_0_OR_GREATER
        string result = """
            // <auto-generated/>
            #pragma warning disable
            #nullable enable
            namespace MyApp
            {
                /// <inheritdoc/>
                partial class MyViewModel<T>
                {
                    /// <inheritdoc cref="content"/>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
                    public T Content
                    {
                        get => content;
                        [global::System.Diagnostics.CodeAnalysis.MemberNotNull("content")]
                        set
                        {
                            if (!global::System.Collections.Generic.EqualityComparer<T>.Default.Equals(content, value))
                            {
                                OnContentChanging(value);
                                OnContentChanging(default, value);
                                OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Content);
                                content = value;
                                OnContentChanged(value);
                                OnContentChanged(default, value);
                                OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Content);
                            }
                        }
                    }

                    /// <summary>Executes the logic for when <see cref="Content"/> is changing.</summary>
                    /// <param name="value">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="Content"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnContentChanging(T value);
                    /// <summary>Executes the logic for when <see cref="Content"/> is changing.</summary>
                    /// <param name="oldValue">The previous property value that is being replaced.</param>
                    /// <param name="newValue">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="Content"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnContentChanging(T? oldValue, T newValue);
                    /// <summary>Executes the logic for when <see cref="Content"/> just changed.</summary>
                    /// <param name="value">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="Content"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnContentChanged(T value);
                    /// <summary>Executes the logic for when <see cref="Content"/> just changed.</summary>
                    /// <param name="oldValue">The previous property value that was replaced.</param>
                    /// <param name="newValue">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="Content"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnContentChanged(T? oldValue, T newValue);
                }
            }
            """;
#else
        string result = """
            // <auto-generated/>
            #pragma warning disable
            #nullable enable
            namespace MyApp
            {
                /// <inheritdoc/>
                partial class MyViewModel<T>
                {
                    /// <inheritdoc cref="content"/>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
                    public T Content
                    {
                        get => content;
                        set
                        {
                            if (!global::System.Collections.Generic.EqualityComparer<T>.Default.Equals(content, value))
                            {
                                OnContentChanging(value);
                                OnContentChanging(default, value);
                                OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Content);
                                content = value;
                                OnContentChanged(value);
                                OnContentChanged(default, value);
                                OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Content);
                            }
                        }
                    }

                    /// <summary>Executes the logic for when <see cref="Content"/> is changing.</summary>
                    /// <param name="value">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="Content"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnContentChanging(T value);
                    /// <summary>Executes the logic for when <see cref="Content"/> is changing.</summary>
                    /// <param name="oldValue">The previous property value that is being replaced.</param>
                    /// <param name="newValue">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="Content"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnContentChanging(T? oldValue, T newValue);
                    /// <summary>Executes the logic for when <see cref="Content"/> just changed.</summary>
                    /// <param name="value">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="Content"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnContentChanged(T value);
                    /// <summary>Executes the logic for when <see cref="Content"/> just changed.</summary>
                    /// <param name="oldValue">The previous property value that was replaced.</param>
                    /// <param name="newValue">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="Content"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnContentChanged(T? oldValue, T newValue);
                }
            }
            """;
#endif

        VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel`1.g.cs", result));
    }

    [TestMethod]
    public void ObservablePropertyWithNonNullableConstrainedValueTypeGenericType_DoesNotEmitMemberNotNullAttribute()
    {
        string source = """
            using System;
            using CommunityToolkit.Mvvm.ComponentModel;

            #nullable enable

            namespace MyApp;
            
            partial class MyViewModel<T> : ObservableObject
                where T : struct
            {
                [ObservableProperty]
                private T content;
            }
            """;

        string result = """
            // <auto-generated/>
            #pragma warning disable
            #nullable enable
            namespace MyApp
            {
                /// <inheritdoc/>
                partial class MyViewModel<T>
                {
                    /// <inheritdoc cref="content"/>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
                    public T Content
                    {
                        get => content;
                        set
                        {
                            if (!global::System.Collections.Generic.EqualityComparer<T>.Default.Equals(content, value))
                            {
                                OnContentChanging(value);
                                OnContentChanging(default, value);
                                OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Content);
                                content = value;
                                OnContentChanged(value);
                                OnContentChanged(default, value);
                                OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Content);
                            }
                        }
                    }

                    /// <summary>Executes the logic for when <see cref="Content"/> is changing.</summary>
                    /// <param name="value">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="Content"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnContentChanging(T value);
                    /// <summary>Executes the logic for when <see cref="Content"/> is changing.</summary>
                    /// <param name="oldValue">The previous property value that is being replaced.</param>
                    /// <param name="newValue">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="Content"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnContentChanging(T oldValue, T newValue);
                    /// <summary>Executes the logic for when <see cref="Content"/> just changed.</summary>
                    /// <param name="value">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="Content"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnContentChanged(T value);
                    /// <summary>Executes the logic for when <see cref="Content"/> just changed.</summary>
                    /// <param name="oldValue">The previous property value that was replaced.</param>
                    /// <param name="newValue">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="Content"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnContentChanged(T oldValue, T newValue);
                }
            }
            """;

        VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel`1.g.cs", result));
    }

    [TestMethod]
    public void ObservablePropertyWithNullableReferenceType_DoesNotEmitMemberNotNullAttribute()
    {
        string source = """
            using CommunityToolkit.Mvvm.ComponentModel;

            #nullable enable

            namespace MyApp;
            
            partial class MyViewModel : ObservableObject
            {
                [ObservableProperty]
                private string? name;
            }
            """;

        string result = """
            // <auto-generated/>
            #pragma warning disable
            #nullable enable
            namespace MyApp
            {
                /// <inheritdoc/>
                partial class MyViewModel
                {
                    /// <inheritdoc cref="name"/>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
                    public string? Name
                    {
                        get => name;
                        set
                        {
                            if (!global::System.Collections.Generic.EqualityComparer<string?>.Default.Equals(name, value))
                            {
                                OnNameChanging(value);
                                OnNameChanging(default, value);
                                OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Name);
                                name = value;
                                OnNameChanged(value);
                                OnNameChanged(default, value);
                                OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Name);
                            }
                        }
                    }

                    /// <summary>Executes the logic for when <see cref="Name"/> is changing.</summary>
                    /// <param name="value">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="Name"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnNameChanging(string? value);
                    /// <summary>Executes the logic for when <see cref="Name"/> is changing.</summary>
                    /// <param name="oldValue">The previous property value that is being replaced.</param>
                    /// <param name="newValue">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="Name"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnNameChanging(string? oldValue, string? newValue);
                    /// <summary>Executes the logic for when <see cref="Name"/> just changed.</summary>
                    /// <param name="value">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="Name"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnNameChanged(string? value);
                    /// <summary>Executes the logic for when <see cref="Name"/> just changed.</summary>
                    /// <param name="oldValue">The previous property value that was replaced.</param>
                    /// <param name="newValue">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="Name"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnNameChanged(string? oldValue, string? newValue);
                }
            }
            """;

        VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result));
    }

    [TestMethod]
    public void ObservablePropertyWithNullableValueType_DoesNotEmitMemberNotNullAttribute()
    {
        string source = """
            using System;
            using CommunityToolkit.Mvvm.ComponentModel;

            #nullable enable

            namespace MyApp;
            
            partial class MyViewModel : ObservableObject
            {
                [ObservableProperty]
                private Guid? id;
            }
            """;

        string result = """
            // <auto-generated/>
            #pragma warning disable
            #nullable enable
            namespace MyApp
            {
                /// <inheritdoc/>
                partial class MyViewModel
                {
                    /// <inheritdoc cref="id"/>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
                    public global::System.Guid? Id
                    {
                        get => id;
                        set
                        {
                            if (!global::System.Collections.Generic.EqualityComparer<global::System.Guid?>.Default.Equals(id, value))
                            {
                                OnIdChanging(value);
                                OnIdChanging(default, value);
                                OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Id);
                                id = value;
                                OnIdChanged(value);
                                OnIdChanged(default, value);
                                OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Id);
                            }
                        }
                    }

                    /// <summary>Executes the logic for when <see cref="Id"/> is changing.</summary>
                    /// <param name="value">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="Id"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnIdChanging(global::System.Guid? value);
                    /// <summary>Executes the logic for when <see cref="Id"/> is changing.</summary>
                    /// <param name="oldValue">The previous property value that is being replaced.</param>
                    /// <param name="newValue">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="Id"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnIdChanging(global::System.Guid? oldValue, global::System.Guid? newValue);
                    /// <summary>Executes the logic for when <see cref="Id"/> just changed.</summary>
                    /// <param name="value">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="Id"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnIdChanged(global::System.Guid? value);
                    /// <summary>Executes the logic for when <see cref="Id"/> just changed.</summary>
                    /// <param name="oldValue">The previous property value that was replaced.</param>
                    /// <param name="newValue">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="Id"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnIdChanged(global::System.Guid? oldValue, global::System.Guid? newValue);
                }
            }
            """;

        VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result));
    }

    [TestMethod]
    public void ObservablePropertyWithNullableUnconstrainedGenericType_DoesNotEmitMemberNotNullAttribute()
    {
        string source = """
            using System;
            using CommunityToolkit.Mvvm.ComponentModel;

            #nullable enable

            namespace MyApp;
            
            partial class MyViewModel<T> : ObservableObject
            {
                [ObservableProperty]
                private T? content;
            }
            """;

        string result = """
            // <auto-generated/>
            #pragma warning disable
            #nullable enable
            namespace MyApp
            {
                /// <inheritdoc/>
                partial class MyViewModel<T>
                {
                    /// <inheritdoc cref="content"/>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
                    public T? Content
                    {
                        get => content;
                        set
                        {
                            if (!global::System.Collections.Generic.EqualityComparer<T?>.Default.Equals(content, value))
                            {
                                OnContentChanging(value);
                                OnContentChanging(default, value);
                                OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Content);
                                content = value;
                                OnContentChanged(value);
                                OnContentChanged(default, value);
                                OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Content);
                            }
                        }
                    }

                    /// <summary>Executes the logic for when <see cref="Content"/> is changing.</summary>
                    /// <param name="value">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="Content"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnContentChanging(T? value);
                    /// <summary>Executes the logic for when <see cref="Content"/> is changing.</summary>
                    /// <param name="oldValue">The previous property value that is being replaced.</param>
                    /// <param name="newValue">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="Content"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnContentChanging(T? oldValue, T? newValue);
                    /// <summary>Executes the logic for when <see cref="Content"/> just changed.</summary>
                    /// <param name="value">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="Content"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnContentChanged(T? value);
                    /// <summary>Executes the logic for when <see cref="Content"/> just changed.</summary>
                    /// <param name="oldValue">The previous property value that was replaced.</param>
                    /// <param name="newValue">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="Content"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnContentChanged(T? oldValue, T? newValue);
                }
            }
            """;

        VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel`1.g.cs", result));
    }

    [TestMethod]
    public void ObservablePropertyWithNullableConstrainedReferenceTypeGenericType_DoesNotEmitMemberNotNullAttribute()
    {
        string source = """
            using System;
            using CommunityToolkit.Mvvm.ComponentModel;

            #nullable enable

            namespace MyApp;
            
            partial class MyViewModel<T> : ObservableObject
                where T : class
            {
                [ObservableProperty]
                private T? content;
            }
            """;

        string result = """
            // <auto-generated/>
            #pragma warning disable
            #nullable enable
            namespace MyApp
            {
                /// <inheritdoc/>
                partial class MyViewModel<T>
                {
                    /// <inheritdoc cref="content"/>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
                    public T? Content
                    {
                        get => content;
                        set
                        {
                            if (!global::System.Collections.Generic.EqualityComparer<T?>.Default.Equals(content, value))
                            {
                                OnContentChanging(value);
                                OnContentChanging(default, value);
                                OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Content);
                                content = value;
                                OnContentChanged(value);
                                OnContentChanged(default, value);
                                OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Content);
                            }
                        }
                    }

                    /// <summary>Executes the logic for when <see cref="Content"/> is changing.</summary>
                    /// <param name="value">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="Content"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnContentChanging(T? value);
                    /// <summary>Executes the logic for when <see cref="Content"/> is changing.</summary>
                    /// <param name="oldValue">The previous property value that is being replaced.</param>
                    /// <param name="newValue">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="Content"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnContentChanging(T? oldValue, T? newValue);
                    /// <summary>Executes the logic for when <see cref="Content"/> just changed.</summary>
                    /// <param name="value">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="Content"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnContentChanged(T? value);
                    /// <summary>Executes the logic for when <see cref="Content"/> just changed.</summary>
                    /// <param name="oldValue">The previous property value that was replaced.</param>
                    /// <param name="newValue">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="Content"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnContentChanged(T? oldValue, T? newValue);
                }
            }
            """;

        VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel`1.g.cs", result));
    }

    [TestMethod]
    public void ObservablePropertyWithNullableConstrainedValueTypeGenericType_DoesNotEmitMemberNotNullAttribute()
    {
        string source = """
            using System;
            using CommunityToolkit.Mvvm.ComponentModel;

            #nullable enable

            namespace MyApp;
            
            partial class MyViewModel<T> : ObservableObject
                where T : struct
            {
                [ObservableProperty]
                private T? content;
            }
            """;

        string result = """
            // <auto-generated/>
            #pragma warning disable
            #nullable enable
            namespace MyApp
            {
                /// <inheritdoc/>
                partial class MyViewModel<T>
                {
                    /// <inheritdoc cref="content"/>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
                    public T? Content
                    {
                        get => content;
                        set
                        {
                            if (!global::System.Collections.Generic.EqualityComparer<T?>.Default.Equals(content, value))
                            {
                                OnContentChanging(value);
                                OnContentChanging(default, value);
                                OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Content);
                                content = value;
                                OnContentChanged(value);
                                OnContentChanged(default, value);
                                OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Content);
                            }
                        }
                    }

                    /// <summary>Executes the logic for when <see cref="Content"/> is changing.</summary>
                    /// <param name="value">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="Content"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnContentChanging(T? value);
                    /// <summary>Executes the logic for when <see cref="Content"/> is changing.</summary>
                    /// <param name="oldValue">The previous property value that is being replaced.</param>
                    /// <param name="newValue">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="Content"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnContentChanging(T? oldValue, T? newValue);
                    /// <summary>Executes the logic for when <see cref="Content"/> just changed.</summary>
                    /// <param name="value">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="Content"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnContentChanged(T? value);
                    /// <summary>Executes the logic for when <see cref="Content"/> just changed.</summary>
                    /// <param name="oldValue">The previous property value that was replaced.</param>
                    /// <param name="newValue">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="Content"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnContentChanged(T? oldValue, T? newValue);
                }
            }
            """;

        VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel`1.g.cs", result));
    }

    // See https://github.com/CommunityToolkit/dotnet/issues/601
    [TestMethod]
    public void ObservablePropertyWithForwardedAttributesWithNumberLiterals_PreservesType()
    {
        string source = """
            using System.ComponentModel;
            using CommunityToolkit.Mvvm.ComponentModel;

            #nullable enable

            namespace MyApp;
            
            partial class MyViewModel : ObservableObject
            {
                const double MyDouble = 3.14;
                const float MyFloat = 3.14f;

                [ObservableProperty]
                [property: DefaultValue(0.0)]
                [property: DefaultValue(1.24)]
                [property: DefaultValue(0.0f)]
                [property: DefaultValue(0.0f)]
                [property: DefaultValue(MyDouble)]
                [property: DefaultValue(MyFloat)]
                private object? a;
            }

            public class DefaultValueAttribute : Attribute
            {
                public DefaultValueAttribute(object value)
                {
                }
            }
            """;

        string result = """
            // <auto-generated/>
            #pragma warning disable
            #nullable enable
            namespace MyApp
            {
                /// <inheritdoc/>
                partial class MyViewModel
                {
                    /// <inheritdoc cref="a"/>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
                    [global::MyApp.DefaultValueAttribute(0D)]
                    [global::MyApp.DefaultValueAttribute(1.24D)]
                    [global::MyApp.DefaultValueAttribute(0F)]
                    [global::MyApp.DefaultValueAttribute(0F)]
                    [global::MyApp.DefaultValueAttribute(3.14D)]
                    [global::MyApp.DefaultValueAttribute(3.14F)]
                    public object? A
                    {
                        get => a;
                        set
                        {
                            if (!global::System.Collections.Generic.EqualityComparer<object?>.Default.Equals(a, value))
                            {
                                OnAChanging(value);
                                OnAChanging(default, value);
                                OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.A);
                                a = value;
                                OnAChanged(value);
                                OnAChanged(default, value);
                                OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.A);
                            }
                        }
                    }

                    /// <summary>Executes the logic for when <see cref="A"/> is changing.</summary>
                    /// <param name="value">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="A"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnAChanging(object? value);
                    /// <summary>Executes the logic for when <see cref="A"/> is changing.</summary>
                    /// <param name="oldValue">The previous property value that is being replaced.</param>
                    /// <param name="newValue">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="A"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnAChanging(object? oldValue, object? newValue);
                    /// <summary>Executes the logic for when <see cref="A"/> just changed.</summary>
                    /// <param name="value">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="A"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnAChanged(object? value);
                    /// <summary>Executes the logic for when <see cref="A"/> just changed.</summary>
                    /// <param name="oldValue">The previous property value that was replaced.</param>
                    /// <param name="newValue">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="A"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnAChanged(object? oldValue, object? newValue);
                }
            }
            """;

        VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result));
    }

    [TestMethod]
    public void RelayCommandMethodWithForwardedAttributesWithNumberLiterals_PreservesType()
    {
        string source = """
            using CommunityToolkit.Mvvm.Input;

            #nullable enable

            namespace MyApp;
            
            partial class MyViewModel
            {
                const double MyDouble = 3.14;
                const float MyFloat = 3.14f;

                [RelayCommand]
                [field: DefaultValue(0.0)]
                [field: DefaultValue(1.24)]
                [field: DefaultValue(0.0f)]
                [field: DefaultValue(0.0f)]
                [field: DefaultValue(MyDouble)]
                [field: DefaultValue(MyFloat)]
                [property: DefaultValue(0.0)]
                [property: DefaultValue(1.24)]
                [property: DefaultValue(0.0f)]
                [property: DefaultValue(0.0f)]
                [property: DefaultValue(MyDouble)]
                [property: DefaultValue(MyFloat)]
                private void Test()
                {
                }
            }

            public class DefaultValueAttribute : Attribute
            {
                public DefaultValueAttribute(object value)
                {
                }
            }
            """;

        string result = """
            // <auto-generated/>
            #pragma warning disable
            #nullable enable
            namespace MyApp
            {
                /// <inheritdoc/>
                partial class MyViewModel
                {
                    /// <summary>The backing field for <see cref="TestCommand"/>.</summary>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator", <ASSEMBLY_VERSION>)]
                    [global::MyApp.DefaultValueAttribute(0D)]
                    [global::MyApp.DefaultValueAttribute(1.24D)]
                    [global::MyApp.DefaultValueAttribute(0F)]
                    [global::MyApp.DefaultValueAttribute(0F)]
                    [global::MyApp.DefaultValueAttribute(3.14D)]
                    [global::MyApp.DefaultValueAttribute(3.14F)]
                    private global::CommunityToolkit.Mvvm.Input.RelayCommand? testCommand;
                    /// <summary>Gets an <see cref="global::CommunityToolkit.Mvvm.Input.IRelayCommand"/> instance wrapping <see cref="Test"/>.</summary>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator", <ASSEMBLY_VERSION>)]
                    [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
                    [global::MyApp.DefaultValueAttribute(0D)]
                    [global::MyApp.DefaultValueAttribute(1.24D)]
                    [global::MyApp.DefaultValueAttribute(0F)]
                    [global::MyApp.DefaultValueAttribute(0F)]
                    [global::MyApp.DefaultValueAttribute(3.14D)]
                    [global::MyApp.DefaultValueAttribute(3.14F)]
                    public global::CommunityToolkit.Mvvm.Input.IRelayCommand TestCommand => testCommand ??= new global::CommunityToolkit.Mvvm.Input.RelayCommand(new global::System.Action(Test));
                }
            }
            """;

        VerifyGenerateSources(source, new[] { new RelayCommandGenerator() }, ("MyApp.MyViewModel.Test.g.cs", result));
    }

    // See https://github.com/CommunityToolkit/dotnet/issues/681
    [TestMethod]
    public void ObservablePropertyWithForwardedAttributesWithEnumWithNegativeValue_WorksCorrectly()
    {
        string source = """
            using System.ComponentModel;
            using CommunityToolkit.Mvvm.ComponentModel;

            #nullable enable

            namespace MyApp;
            
            partial class MyViewModel : ObservableObject
            {
                [ObservableProperty]
                [property: DefaultValue(NegativeEnum1.Problem)]
                [property: DefaultValue(NegativeEnum2.Problem)]
                [property: DefaultValue(NegativeEnum3.Problem)]
                [property: DefaultValue(NegativeEnum4.Problem)]
                private object? a;
            }

            public class DefaultValueAttribute : Attribute
            {
                public DefaultValueAttribute(object value)
                {
                }
            }

            public enum NegativeEnum1
            {
                Problem = -1073741824,
                OK = 0
            }

            public enum NegativeEnum2 : long
            {
                Problem = long.MinValue,
                OK = 0
            }

            public enum NegativeEnum3 : short
            {
                Problem = -1234,
                OK = 0
            }

            public enum NegativeEnum4 : sbyte
            {
                Problem = -1,
                OK = 0
            }
            """;

        string result = """
            // <auto-generated/>
            #pragma warning disable
            #nullable enable
            namespace MyApp
            {
                /// <inheritdoc/>
                partial class MyViewModel
                {
                    /// <inheritdoc cref="a"/>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
                    [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum1)(-1073741824))]
                    [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum2)(-9223372036854775808))]
                    [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum3)(-1234))]
                    [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum4)(-1))]
                    public object? A
                    {
                        get => a;
                        set
                        {
                            if (!global::System.Collections.Generic.EqualityComparer<object?>.Default.Equals(a, value))
                            {
                                OnAChanging(value);
                                OnAChanging(default, value);
                                OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.A);
                                a = value;
                                OnAChanged(value);
                                OnAChanged(default, value);
                                OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.A);
                            }
                        }
                    }

                    /// <summary>Executes the logic for when <see cref="A"/> is changing.</summary>
                    /// <param name="value">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="A"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnAChanging(object? value);
                    /// <summary>Executes the logic for when <see cref="A"/> is changing.</summary>
                    /// <param name="oldValue">The previous property value that is being replaced.</param>
                    /// <param name="newValue">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="A"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnAChanging(object? oldValue, object? newValue);
                    /// <summary>Executes the logic for when <see cref="A"/> just changed.</summary>
                    /// <param name="value">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="A"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnAChanged(object? value);
                    /// <summary>Executes the logic for when <see cref="A"/> just changed.</summary>
                    /// <param name="oldValue">The previous property value that was replaced.</param>
                    /// <param name="newValue">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="A"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnAChanged(object? oldValue, object? newValue);
                }
            }
            """;

        VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result));
    }

    [TestMethod]
    public void ObservablePropertyWithForwardedAttributesWithEnumWithCombinedValues_WorksCorrectly()
    {
        string source = """
            using System.ComponentModel;
            using CommunityToolkit.Mvvm.ComponentModel;

            #nullable enable

            namespace MyApp;
            
            partial class MyViewModel : ObservableObject
            {
                [ObservableProperty]
                [property: DefaultValue(NegativeEnum1.A)]
                [property: DefaultValue(NegativeEnum1.B)]
                [property: DefaultValue((NegativeEnum1)42)]
                [property: DefaultValue((NegativeEnum1)OtherClass.MyNumber)]
                [property: DefaultValue(OtherClass.MyEnumValue)]
                [property: DefaultValue(NegativeEnum2.A)]
                [property: DefaultValue(NegativeEnum2.B)]
                [property: DefaultValue((NegativeEnum2)42)]
                [property: DefaultValue((NegativeEnum2)OtherClass.MyNumber)]
                [property: DefaultValue((NegativeEnum2)(int)OtherClass.MyEnumValue)]
                [property: DefaultValue(NegativeEnum3.A)]
                [property: DefaultValue(NegativeEnum3.B)]
                [property: DefaultValue((NegativeEnum3)42)]
                [property: DefaultValue((NegativeEnum3)OtherClass.MyNumber)]
                [property: DefaultValue((NegativeEnum3)(int)OtherClass.MyEnumValue)]
                [property: DefaultValue(NegativeEnum4.A)]
                [property: DefaultValue(NegativeEnum4.B)]
                [property: DefaultValue((NegativeEnum4)42)]
                [property: DefaultValue((NegativeEnum4)unchecked((sbyte)OtherClass.MyNumber))]
                [property: DefaultValue((NegativeEnum4)(int)OtherClass.MyEnumValue)]
                private object? a;
            }

            public static class OtherClass
            {
                public const int MyNumber = 456;
                public const NegativeEnum1 MyEnumValue = NegativeEnum1.C;
            }

            public class DefaultValueAttribute : Attribute
            {
                public DefaultValueAttribute(object value)
                {
                }
            }

            public enum NegativeEnum1
            {
                A = 0,
                B = -1073741824,
                C = 123
            }

            public enum NegativeEnum2 : long
            {
                A = 0,
                B = long.MinValue
            }

            public enum NegativeEnum3 : short
            {
                A = 1,
                B = -1234
            }

            public enum NegativeEnum4 : sbyte
            {
                A = 1,
                B = -1
            }
            """;

        string result = """
            // <auto-generated/>
            #pragma warning disable
            #nullable enable
            namespace MyApp
            {
                /// <inheritdoc/>
                partial class MyViewModel
                {
                    /// <inheritdoc cref="a"/>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
                    [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum1)0)]
                    [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum1)(-1073741824))]
                    [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum1)42)]
                    [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum1)456)]
                    [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum1)123)]
                    [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum2)0)]
                    [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum2)(-9223372036854775808))]
                    [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum2)42)]
                    [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum2)456)]
                    [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum2)123)]
                    [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum3)1)]
                    [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum3)(-1234))]
                    [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum3)42)]
                    [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum3)456)]
                    [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum3)123)]
                    [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum4)1)]
                    [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum4)(-1))]
                    [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum4)42)]
                    [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum4)(-56))]
                    [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum4)123)]
                    public object? A
                    {
                        get => a;
                        set
                        {
                            if (!global::System.Collections.Generic.EqualityComparer<object?>.Default.Equals(a, value))
                            {
                                OnAChanging(value);
                                OnAChanging(default, value);
                                OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.A);
                                a = value;
                                OnAChanged(value);
                                OnAChanged(default, value);
                                OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.A);
                            }
                        }
                    }

                    /// <summary>Executes the logic for when <see cref="A"/> is changing.</summary>
                    /// <param name="value">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="A"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnAChanging(object? value);
                    /// <summary>Executes the logic for when <see cref="A"/> is changing.</summary>
                    /// <param name="oldValue">The previous property value that is being replaced.</param>
                    /// <param name="newValue">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="A"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnAChanging(object? oldValue, object? newValue);
                    /// <summary>Executes the logic for when <see cref="A"/> just changed.</summary>
                    /// <param name="value">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="A"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnAChanged(object? value);
                    /// <summary>Executes the logic for when <see cref="A"/> just changed.</summary>
                    /// <param name="oldValue">The previous property value that was replaced.</param>
                    /// <param name="newValue">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="A"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnAChanged(object? oldValue, object? newValue);
                }
            }
            """;

        VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result));
    }

    // See https://github.com/CommunityToolkit/dotnet/issues/632
    [TestMethod]
    public void RelayCommandMethodWithPartialDeclarations_TriggersCorrectly()
    {
        string source = """
            using CommunityToolkit.Mvvm.Input;

            #nullable enable

            namespace MyApp;
            
            partial class MyViewModel
            {
                [RelayCommand]
                private partial void Test1()
                {
                }

                private partial void Test1();

                private partial void Test2()
                {
                }

                [RelayCommand]
                private partial void Test2();
            }
            """;

        string result1 = """
            // <auto-generated/>
            #pragma warning disable
            #nullable enable
            namespace MyApp
            {
                /// <inheritdoc/>
                partial class MyViewModel
                {
                    /// <summary>The backing field for <see cref="Test1Command"/>.</summary>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator", <ASSEMBLY_VERSION>)]
                    private global::CommunityToolkit.Mvvm.Input.RelayCommand? test1Command;
                    /// <summary>Gets an <see cref="global::CommunityToolkit.Mvvm.Input.IRelayCommand"/> instance wrapping <see cref="Test1"/>.</summary>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator", <ASSEMBLY_VERSION>)]
                    [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
                    public global::CommunityToolkit.Mvvm.Input.IRelayCommand Test1Command => test1Command ??= new global::CommunityToolkit.Mvvm.Input.RelayCommand(new global::System.Action(Test1));
                }
            }
            """;

        string result2 = """
            // <auto-generated/>
            #pragma warning disable
            #nullable enable
            namespace MyApp
            {
                /// <inheritdoc/>
                partial class MyViewModel
                {
                    /// <summary>The backing field for <see cref="Test2Command"/>.</summary>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator", <ASSEMBLY_VERSION>)]
                    private global::CommunityToolkit.Mvvm.Input.RelayCommand? test2Command;
                    /// <summary>Gets an <see cref="global::CommunityToolkit.Mvvm.Input.IRelayCommand"/> instance wrapping <see cref="Test2"/>.</summary>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator", <ASSEMBLY_VERSION>)]
                    [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
                    public global::CommunityToolkit.Mvvm.Input.IRelayCommand Test2Command => test2Command ??= new global::CommunityToolkit.Mvvm.Input.RelayCommand(new global::System.Action(Test2));
                }
            }
            """;

        VerifyGenerateSources(source, new[] { new RelayCommandGenerator() }, ("MyApp.MyViewModel.Test1.g.cs", result1), ("MyApp.MyViewModel.Test2.g.cs", result2));
    }

    // See https://github.com/CommunityToolkit/dotnet/issues/632
    [TestMethod]
    public void RelayCommandMethodWithForwardedAttributesOverPartialDeclarations_MergesAttributes()
    {
        string source = """
            using CommunityToolkit.Mvvm.Input;

            #nullable enable

            namespace MyApp;
            
            partial class MyViewModel
            {
                [RelayCommand]
                [field: Value(0)]
                [property: Value(1)]
                private partial void Test1()
                {
                }

                [field: Value(2)]
                [property: Value(3)]
                private partial void Test1();

                [field: Value(0)]
                [property: Value(1)]
                private partial void Test2()
                {
                }

                [RelayCommand]
                [field: Value(2)]
                [property: Value(3)]
                private partial void Test2();
            }

            public class ValueAttribute : Attribute
            {
                public ValueAttribute(object value)
                {
                }
            }
            """;

        string result1 = """
            // <auto-generated/>
            #pragma warning disable
            #nullable enable
            namespace MyApp
            {
                /// <inheritdoc/>
                partial class MyViewModel
                {
                    /// <summary>The backing field for <see cref="Test1Command"/>.</summary>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator", <ASSEMBLY_VERSION>)]
                    [global::MyApp.ValueAttribute(2)]
                    [global::MyApp.ValueAttribute(0)]
                    private global::CommunityToolkit.Mvvm.Input.RelayCommand? test1Command;
                    /// <summary>Gets an <see cref="global::CommunityToolkit.Mvvm.Input.IRelayCommand"/> instance wrapping <see cref="Test1"/>.</summary>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator", <ASSEMBLY_VERSION>)]
                    [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
                    [global::MyApp.ValueAttribute(3)]
                    [global::MyApp.ValueAttribute(1)]
                    public global::CommunityToolkit.Mvvm.Input.IRelayCommand Test1Command => test1Command ??= new global::CommunityToolkit.Mvvm.Input.RelayCommand(new global::System.Action(Test1));
                }
            }
            """;

        string result2 = """
            // <auto-generated/>
            #pragma warning disable
            #nullable enable
            namespace MyApp
            {
                /// <inheritdoc/>
                partial class MyViewModel
                {
                    /// <summary>The backing field for <see cref="Test2Command"/>.</summary>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator", <ASSEMBLY_VERSION>)]
                    [global::MyApp.ValueAttribute(2)]
                    [global::MyApp.ValueAttribute(0)]
                    private global::CommunityToolkit.Mvvm.Input.RelayCommand? test2Command;
                    /// <summary>Gets an <see cref="global::CommunityToolkit.Mvvm.Input.IRelayCommand"/> instance wrapping <see cref="Test2"/>.</summary>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator", <ASSEMBLY_VERSION>)]
                    [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
                    [global::MyApp.ValueAttribute(3)]
                    [global::MyApp.ValueAttribute(1)]
                    public global::CommunityToolkit.Mvvm.Input.IRelayCommand Test2Command => test2Command ??= new global::CommunityToolkit.Mvvm.Input.RelayCommand(new global::System.Action(Test2));
                }
            }
            """;

        VerifyGenerateSources(source, new[] { new RelayCommandGenerator() }, ("MyApp.MyViewModel.Test1.g.cs", result1), ("MyApp.MyViewModel.Test2.g.cs", result2));
    }

    // See https://github.com/CommunityToolkit/dotnet/issues/681
    [TestMethod]
    public void RelayCommandMethodWithForwardedAttributesWithEnumValues_WorksCorrectly()
    {
        string source = """
            using CommunityToolkit.Mvvm.Input;

            #nullable enable

            namespace MyApp;
            
            partial class MyViewModel
            {
                [RelayCommand]
                [field: DefaultValue(NegativeEnum1.Problem)]
                [field: DefaultValue(NegativeEnum2.Problem)]
                [field: DefaultValue(NegativeEnum3.Problem)]
                [field: DefaultValue(NegativeEnum4.Problem)]
                [property: DefaultValue(NegativeEnum1.Problem)]
                [property: DefaultValue(NegativeEnum2.Problem)]
                [property: DefaultValue(NegativeEnum3.Problem)]
                [property: DefaultValue(NegativeEnum4.Problem)]
                private void Test()
                {
                }
            }

            public class DefaultValueAttribute : Attribute
            {
                public DefaultValueAttribute(object value)
                {
                }
            }

            public enum NegativeEnum1
            {
                Problem = -1073741824,
                OK = 0
            }

            public enum NegativeEnum2 : long
            {
                Problem = long.MinValue,
                OK = 0
            }

            public enum NegativeEnum3 : short
            {
                Problem = -1234,
                OK = 0
            }

            public enum NegativeEnum4 : sbyte
            {
                Problem = -1,
                OK = 0
            }
            """;

        string result = """
            // <auto-generated/>
            #pragma warning disable
            #nullable enable
            namespace MyApp
            {
                /// <inheritdoc/>
                partial class MyViewModel
                {
                    /// <summary>The backing field for <see cref="TestCommand"/>.</summary>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator", <ASSEMBLY_VERSION>)]
                    [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum1)(-1073741824))]
                    [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum2)(-9223372036854775808))]
                    [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum3)(-1234))]
                    [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum4)(-1))]
                    private global::CommunityToolkit.Mvvm.Input.RelayCommand? testCommand;
                    /// <summary>Gets an <see cref="global::CommunityToolkit.Mvvm.Input.IRelayCommand"/> instance wrapping <see cref="Test"/>.</summary>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator", <ASSEMBLY_VERSION>)]
                    [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
                    [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum1)(-1073741824))]
                    [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum2)(-9223372036854775808))]
                    [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum3)(-1234))]
                    [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum4)(-1))]
                    public global::CommunityToolkit.Mvvm.Input.IRelayCommand TestCommand => testCommand ??= new global::CommunityToolkit.Mvvm.Input.RelayCommand(new global::System.Action(Test));
                }
            }
            """;

        VerifyGenerateSources(source, new[] { new RelayCommandGenerator() }, ("MyApp.MyViewModel.Test.g.cs", result));
    }

    [TestMethod]
    public void ObservablePropertyWithinGenericAndNestedTypes()
    {
        string source = """
            using System.ComponentModel;
            using CommunityToolkit.Mvvm.ComponentModel;

            #nullable enable

            namespace MyApp;
            
            partial class Foo
            {
                partial class MyViewModel<T> : ObservableObject
                {
                    [ObservableProperty]
                    private string? a;
                }
            }
            """;

        string result = """
            // <auto-generated/>
            #pragma warning disable
            #nullable enable
            namespace MyApp
            {
                /// <inheritdoc/>
                partial class Foo
                {
                    /// <inheritdoc/>
                    partial class MyViewModel<T>
                    {
                        /// <inheritdoc cref="a"/>
                        [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                        [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
                        public string? A
                        {
                            get => a;
                            set
                            {
                                if (!global::System.Collections.Generic.EqualityComparer<string?>.Default.Equals(a, value))
                                {
                                    OnAChanging(value);
                                    OnAChanging(default, value);
                                    OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.A);
                                    a = value;
                                    OnAChanged(value);
                                    OnAChanged(default, value);
                                    OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.A);
                                }
                            }
                        }

                        /// <summary>Executes the logic for when <see cref="A"/> is changing.</summary>
                        /// <param name="value">The new property value being set.</param>
                        /// <remarks>This method is invoked right before the value of <see cref="A"/> is changed.</remarks>
                        [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                        partial void OnAChanging(string? value);
                        /// <summary>Executes the logic for when <see cref="A"/> is changing.</summary>
                        /// <param name="oldValue">The previous property value that is being replaced.</param>
                        /// <param name="newValue">The new property value being set.</param>
                        /// <remarks>This method is invoked right before the value of <see cref="A"/> is changed.</remarks>
                        [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                        partial void OnAChanging(string? oldValue, string? newValue);
                        /// <summary>Executes the logic for when <see cref="A"/> just changed.</summary>
                        /// <param name="value">The new property value that was set.</param>
                        /// <remarks>This method is invoked right after the value of <see cref="A"/> is changed.</remarks>
                        [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                        partial void OnAChanged(string? value);
                        /// <summary>Executes the logic for when <see cref="A"/> just changed.</summary>
                        /// <param name="oldValue">The previous property value that was replaced.</param>
                        /// <param name="newValue">The new property value that was set.</param>
                        /// <remarks>This method is invoked right after the value of <see cref="A"/> is changed.</remarks>
                        [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                        partial void OnAChanged(string? oldValue, string? newValue);
                    }
                }
            }
            """;

#if ROSLYN_4_3_1_OR_GREATER
        VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.Foo+MyViewModel`1.g.cs", result));
#else
        VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.Foo.MyViewModel_1.g.cs", result));
#endif
    }

    [TestMethod]
    public void ObservableProperty_ValidateGeneratedCachedArguments()
    {
        string source = """
            using System.ComponentModel;
            using CommunityToolkit.Mvvm.ComponentModel;

            namespace MyApp;
            
            partial class MyViewModel : ObservableObject
            {
                [ObservableProperty]
                private string? name;
            }
            """;

        string result = """
            // <auto-generated/>
            #pragma warning disable
            #nullable enable
            namespace MyApp
            {
                /// <inheritdoc/>
                partial class MyViewModel
                {
                    /// <inheritdoc cref="name"/>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
                    public string? Name
                    {
                        get => name;
                        set
                        {
                            if (!global::System.Collections.Generic.EqualityComparer<string?>.Default.Equals(name, value))
                            {
                                OnNameChanging(value);
                                OnNameChanging(default, value);
                                OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Name);
                                name = value;
                                OnNameChanged(value);
                                OnNameChanged(default, value);
                                OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Name);
                            }
                        }
                    }

                    /// <summary>Executes the logic for when <see cref="Name"/> is changing.</summary>
                    /// <param name="value">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="Name"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnNameChanging(string? value);
                    /// <summary>Executes the logic for when <see cref="Name"/> is changing.</summary>
                    /// <param name="oldValue">The previous property value that is being replaced.</param>
                    /// <param name="newValue">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="Name"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnNameChanging(string? oldValue, string? newValue);
                    /// <summary>Executes the logic for when <see cref="Name"/> just changed.</summary>
                    /// <param name="value">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="Name"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnNameChanged(string? value);
                    /// <summary>Executes the logic for when <see cref="Name"/> just changed.</summary>
                    /// <param name="oldValue">The previous property value that was replaced.</param>
                    /// <param name="newValue">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="Name"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnNameChanged(string? oldValue, string? newValue);
                }
            }
            """;

        string changingArgs = """
            // <auto-generated/>
            #pragma warning disable
            #nullable enable
            namespace CommunityToolkit.Mvvm.ComponentModel.__Internals
            {
                /// <summary>
                /// A helper type providing cached, reusable <see cref="global::System.ComponentModel.PropertyChangingEventArgs"/> instances
                /// for all properties generated with <see cref="global::CommunityToolkit.Mvvm.ComponentModel.ObservablePropertyAttribute"/>.
                /// </summary>
                [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                [global::System.Diagnostics.DebuggerNonUserCode]
                [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
                [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
                [global::System.Obsolete("This type is not intended to be used directly by user code")]
                internal static class __KnownINotifyPropertyChangingArgs
                {
                    /// <summary>The cached <see cref="global::System.ComponentModel.PropertyChangingEventArgs"/> instance for all "Name" generated properties.</summary>
                    [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
                    [global::System.Obsolete("This field is not intended to be referenced directly by user code")]
                    public static readonly global::System.ComponentModel.PropertyChangingEventArgs Name = new global::System.ComponentModel.PropertyChangingEventArgs("Name");
                }
            }
            """;

        string changedArgs = """
            // <auto-generated/>
            #pragma warning disable
            #nullable enable
            namespace CommunityToolkit.Mvvm.ComponentModel.__Internals
            {
                /// <summary>
                /// A helper type providing cached, reusable <see cref="global::System.ComponentModel.PropertyChangedEventArgs"/> instances
                /// for all properties generated with <see cref="global::CommunityToolkit.Mvvm.ComponentModel.ObservablePropertyAttribute"/>.
                /// </summary>
                [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                [global::System.Diagnostics.DebuggerNonUserCode]
                [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
                [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
                [global::System.Obsolete("This type is not intended to be used directly by user code")]
                internal static class __KnownINotifyPropertyChangedArgs
                {
                    /// <summary>The cached <see cref="global::System.ComponentModel.PropertyChangedEventArgs"/> instance for all "Name" generated properties.</summary>
                    [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
                    [global::System.Obsolete("This field is not intended to be referenced directly by user code")]
                    public static readonly global::System.ComponentModel.PropertyChangedEventArgs Name = new global::System.ComponentModel.PropertyChangedEventArgs("Name");
                }
            }
            """;

        VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result), ("__KnownINotifyPropertyChangingArgs.g.cs", changingArgs), ("__KnownINotifyPropertyChangedArgs.g.cs", changedArgs));
    }

    [TestMethod]
    public void RelayCommandMethodInTopLevelTypeWithNoNamespace_PreservesInheritdoc()
    {
        string source = """
            using CommunityToolkit.Mvvm.Input;
            
            partial class MyViewModel
            {
                [RelayCommand]
                private void Test()
                {
                }
            }
            """;

        string result = """
            // <auto-generated/>
            #pragma warning disable
            #nullable enable
            /// <inheritdoc/>
            partial class MyViewModel
            {
                /// <summary>The backing field for <see cref="TestCommand"/>.</summary>
                [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator", <ASSEMBLY_VERSION>)]
                private global::CommunityToolkit.Mvvm.Input.RelayCommand? testCommand;
                /// <summary>Gets an <see cref="global::CommunityToolkit.Mvvm.Input.IRelayCommand"/> instance wrapping <see cref="Test"/>.</summary>
                [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator", <ASSEMBLY_VERSION>)]
                [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
                public global::CommunityToolkit.Mvvm.Input.IRelayCommand TestCommand => testCommand ??= new global::CommunityToolkit.Mvvm.Input.RelayCommand(new global::System.Action(Test));
            }
            """;

        VerifyGenerateSources(source, new[] { new RelayCommandGenerator() }, ("MyViewModel.Test.g.cs", result));
    }

    [TestMethod]
    public void ObservableProperty_AnnotatedFieldHasValueIdentifier()
    {
        string source = """
            using CommunityToolkit.Mvvm.ComponentModel;

            namespace MyApp;
            
            partial class MyViewModel : ObservableObject
            {
                [ObservableProperty]
                double value;
            }
            """;

        string result = """
            // <auto-generated/>
            #pragma warning disable
            #nullable enable
            namespace MyApp
            {
                /// <inheritdoc/>
                partial class MyViewModel
                {
                    /// <inheritdoc cref="value"/>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
                    public double Value
                    {
                        get => value;
                        set
                        {
                            if (!global::System.Collections.Generic.EqualityComparer<double>.Default.Equals(this.value, value))
                            {
                                OnValueChanging(value);
                                OnValueChanging(default, value);
                                OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Value);
                                this.value = value;
                                OnValueChanged(value);
                                OnValueChanged(default, value);
                                OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Value);
                            }
                        }
                    }

                    /// <summary>Executes the logic for when <see cref="Value"/> is changing.</summary>
                    /// <param name="value">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="Value"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnValueChanging(double value);
                    /// <summary>Executes the logic for when <see cref="Value"/> is changing.</summary>
                    /// <param name="oldValue">The previous property value that is being replaced.</param>
                    /// <param name="newValue">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="Value"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnValueChanging(double oldValue, double newValue);
                    /// <summary>Executes the logic for when <see cref="Value"/> just changed.</summary>
                    /// <param name="value">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="Value"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnValueChanged(double value);
                    /// <summary>Executes the logic for when <see cref="Value"/> just changed.</summary>
                    /// <param name="oldValue">The previous property value that was replaced.</param>
                    /// <param name="newValue">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="Value"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnValueChanged(double oldValue, double newValue);
                }
            }
            """;

        VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result));
    }

    [TestMethod]
    public void ObservableProperty_AnnotatedFieldHasValueIdentifier_WithChangedMethods()
    {
        string source = """
            using CommunityToolkit.Mvvm.ComponentModel;

            namespace MyApp;
            
            partial class MyViewModel : ObservableObject
            {
                [ObservableProperty]
                double value;

                partial void OnValueChanged(double oldValue, double NewValue)
                {
                }
            }
            """;

        string result = """
            // <auto-generated/>
            #pragma warning disable
            #nullable enable
            namespace MyApp
            {
                /// <inheritdoc/>
                partial class MyViewModel
                {
                    /// <inheritdoc cref="value"/>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
                    public double Value
                    {
                        get => value;
                        set
                        {
                            if (!global::System.Collections.Generic.EqualityComparer<double>.Default.Equals(this.value, value))
                            {
                                double __oldValue = this.value;
                                OnValueChanging(value);
                                OnValueChanging(__oldValue, value);
                                OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Value);
                                this.value = value;
                                OnValueChanged(value);
                                OnValueChanged(__oldValue, value);
                                OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Value);
                            }
                        }
                    }

                    /// <summary>Executes the logic for when <see cref="Value"/> is changing.</summary>
                    /// <param name="value">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="Value"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnValueChanging(double value);
                    /// <summary>Executes the logic for when <see cref="Value"/> is changing.</summary>
                    /// <param name="oldValue">The previous property value that is being replaced.</param>
                    /// <param name="newValue">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="Value"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnValueChanging(double oldValue, double newValue);
                    /// <summary>Executes the logic for when <see cref="Value"/> just changed.</summary>
                    /// <param name="value">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="Value"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnValueChanged(double value);
                    /// <summary>Executes the logic for when <see cref="Value"/> just changed.</summary>
                    /// <param name="oldValue">The previous property value that was replaced.</param>
                    /// <param name="newValue">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="Value"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnValueChanged(double oldValue, double newValue);
                }
            }
            """;

        VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result));
    }

    // See https://github.com/CommunityToolkit/dotnet/issues/710
    [TestMethod]
    public void ObservableProperty_AnnotatedFieldWithEscapedIdentifier()
    {
        string source = """
            using CommunityToolkit.Mvvm.ComponentModel;

            namespace MyApp;
            
            partial class MyViewModel : ObservableObject
            {
                [ObservableProperty]
                double @event;
            }
            """;

        string result = """
            // <auto-generated/>
            #pragma warning disable
            #nullable enable
            namespace MyApp
            {
                /// <inheritdoc/>
                partial class MyViewModel
                {
                    /// <inheritdoc cref="@event"/>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
                    public double Event
                    {
                        get => @event;
                        set
                        {
                            if (!global::System.Collections.Generic.EqualityComparer<double>.Default.Equals(@event, value))
                            {
                                OnEventChanging(value);
                                OnEventChanging(default, value);
                                OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Event);
                                @event = value;
                                OnEventChanged(value);
                                OnEventChanged(default, value);
                                OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Event);
                            }
                        }
                    }

                    /// <summary>Executes the logic for when <see cref="Event"/> is changing.</summary>
                    /// <param name="value">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="Event"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnEventChanging(double value);
                    /// <summary>Executes the logic for when <see cref="Event"/> is changing.</summary>
                    /// <param name="oldValue">The previous property value that is being replaced.</param>
                    /// <param name="newValue">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="Event"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnEventChanging(double oldValue, double newValue);
                    /// <summary>Executes the logic for when <see cref="Event"/> just changed.</summary>
                    /// <param name="value">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="Event"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnEventChanged(double value);
                    /// <summary>Executes the logic for when <see cref="Event"/> just changed.</summary>
                    /// <param name="oldValue">The previous property value that was replaced.</param>
                    /// <param name="newValue">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="Event"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnEventChanged(double oldValue, double newValue);
                }
            }
            """;

        VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result));
    }

    [TestMethod]
    public void ObservableProperty_AnnotatedFieldWithEscapedIdentifier_WithChangedMethods()
    {
        string source = """
            using CommunityToolkit.Mvvm.ComponentModel;

            namespace MyApp;
            
            partial class MyViewModel : ObservableObject
            {
                [ObservableProperty]
                double @object;

                partial void OnObjectChanged(object oldValue, object NewValue)
                {
                }
            }
            """;

        string result = """
            // <auto-generated/>
            #pragma warning disable
            #nullable enable
            namespace MyApp
            {
                /// <inheritdoc/>
                partial class MyViewModel
                {
                    /// <inheritdoc cref="@object"/>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
                    public double Object
                    {
                        get => @object;
                        set
                        {
                            if (!global::System.Collections.Generic.EqualityComparer<double>.Default.Equals(@object, value))
                            {
                                double __oldValue = @object;
                                OnObjectChanging(value);
                                OnObjectChanging(__oldValue, value);
                                OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Object);
                                @object = value;
                                OnObjectChanged(value);
                                OnObjectChanged(__oldValue, value);
                                OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Object);
                            }
                        }
                    }

                    /// <summary>Executes the logic for when <see cref="Object"/> is changing.</summary>
                    /// <param name="value">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="Object"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnObjectChanging(double value);
                    /// <summary>Executes the logic for when <see cref="Object"/> is changing.</summary>
                    /// <param name="oldValue">The previous property value that is being replaced.</param>
                    /// <param name="newValue">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="Object"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnObjectChanging(double oldValue, double newValue);
                    /// <summary>Executes the logic for when <see cref="Object"/> just changed.</summary>
                    /// <param name="value">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="Object"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnObjectChanged(double value);
                    /// <summary>Executes the logic for when <see cref="Object"/> just changed.</summary>
                    /// <param name="oldValue">The previous property value that was replaced.</param>
                    /// <param name="newValue">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="Object"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnObjectChanged(double oldValue, double newValue);
                }
            }
            """;

        VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result));
    }

    // See https://github.com/CommunityToolkit/dotnet/issues/711
    [TestMethod]
    public void ObservableProperty_NotifyPropertyChangedFor_WithNotifyPropertyChanging()
    {
        // Using integers for properties to avoid needing conditional code in the expected results for nullability attributes.
        // This test and the one below are only validating the generation related to INotifyPropertyChanging, and nothing else.
        string source = """
            using System;
            using CommunityToolkit.Mvvm.ComponentModel;

            #nullable enable

            namespace MyApp;
            
            partial class MyViewModel : ObservableObject
            {
                [ObservableProperty]
                [NotifyPropertyChangedFor(nameof(FullName))]
                private int firstName;

                [ObservableProperty]
                [NotifyPropertyChangedFor(nameof(FullName))]
                private int lastName;

                public string FullName => "";
            }
            """;

        string result = """
            // <auto-generated/>
            #pragma warning disable
            #nullable enable
            namespace MyApp
            {
                /// <inheritdoc/>
                partial class MyViewModel
                {
                    /// <inheritdoc cref="firstName"/>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
                    public int FirstName
                    {
                        get => firstName;
                        set
                        {
                            if (!global::System.Collections.Generic.EqualityComparer<int>.Default.Equals(firstName, value))
                            {
                                OnFirstNameChanging(value);
                                OnFirstNameChanging(default, value);
                                OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.FirstName);
                                OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.FullName);
                                firstName = value;
                                OnFirstNameChanged(value);
                                OnFirstNameChanged(default, value);
                                OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.FirstName);
                                OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.FullName);
                            }
                        }
                    }

                    /// <inheritdoc cref="lastName"/>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
                    public int LastName
                    {
                        get => lastName;
                        set
                        {
                            if (!global::System.Collections.Generic.EqualityComparer<int>.Default.Equals(lastName, value))
                            {
                                OnLastNameChanging(value);
                                OnLastNameChanging(default, value);
                                OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.LastName);
                                OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.FullName);
                                lastName = value;
                                OnLastNameChanged(value);
                                OnLastNameChanged(default, value);
                                OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.LastName);
                                OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.FullName);
                            }
                        }
                    }

                    /// <summary>Executes the logic for when <see cref="FirstName"/> is changing.</summary>
                    /// <param name="value">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="FirstName"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnFirstNameChanging(int value);
                    /// <summary>Executes the logic for when <see cref="FirstName"/> is changing.</summary>
                    /// <param name="oldValue">The previous property value that is being replaced.</param>
                    /// <param name="newValue">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="FirstName"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnFirstNameChanging(int oldValue, int newValue);
                    /// <summary>Executes the logic for when <see cref="FirstName"/> just changed.</summary>
                    /// <param name="value">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="FirstName"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnFirstNameChanged(int value);
                    /// <summary>Executes the logic for when <see cref="FirstName"/> just changed.</summary>
                    /// <param name="oldValue">The previous property value that was replaced.</param>
                    /// <param name="newValue">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="FirstName"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnFirstNameChanged(int oldValue, int newValue);
                    /// <summary>Executes the logic for when <see cref="LastName"/> is changing.</summary>
                    /// <param name="value">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="LastName"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnLastNameChanging(int value);
                    /// <summary>Executes the logic for when <see cref="LastName"/> is changing.</summary>
                    /// <param name="oldValue">The previous property value that is being replaced.</param>
                    /// <param name="newValue">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="LastName"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnLastNameChanging(int oldValue, int newValue);
                    /// <summary>Executes the logic for when <see cref="LastName"/> just changed.</summary>
                    /// <param name="value">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="LastName"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnLastNameChanged(int value);
                    /// <summary>Executes the logic for when <see cref="LastName"/> just changed.</summary>
                    /// <param name="oldValue">The previous property value that was replaced.</param>
                    /// <param name="newValue">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="LastName"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                    partial void OnLastNameChanged(int oldValue, int newValue);
                }
            }
            """;

        string changingArgs = """
            // <auto-generated/>
            #pragma warning disable
            #nullable enable
            namespace CommunityToolkit.Mvvm.ComponentModel.__Internals
            {
                /// <summary>
                /// A helper type providing cached, reusable <see cref="global::System.ComponentModel.PropertyChangingEventArgs"/> instances
                /// for all properties generated with <see cref="global::CommunityToolkit.Mvvm.ComponentModel.ObservablePropertyAttribute"/>.
                /// </summary>
                [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                [global::System.Diagnostics.DebuggerNonUserCode]
                [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
                [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
                [global::System.Obsolete("This type is not intended to be used directly by user code")]
                internal static class __KnownINotifyPropertyChangingArgs
                {
                    /// <summary>The cached <see cref="global::System.ComponentModel.PropertyChangingEventArgs"/> instance for all "FirstName" generated properties.</summary>
                    [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
                    [global::System.Obsolete("This field is not intended to be referenced directly by user code")]
                    public static readonly global::System.ComponentModel.PropertyChangingEventArgs FirstName = new global::System.ComponentModel.PropertyChangingEventArgs("FirstName");
                    /// <summary>The cached <see cref="global::System.ComponentModel.PropertyChangingEventArgs"/> instance for all "FullName" generated properties.</summary>
                    [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
                    [global::System.Obsolete("This field is not intended to be referenced directly by user code")]
                    public static readonly global::System.ComponentModel.PropertyChangingEventArgs FullName = new global::System.ComponentModel.PropertyChangingEventArgs("FullName");
                    /// <summary>The cached <see cref="global::System.ComponentModel.PropertyChangingEventArgs"/> instance for all "LastName" generated properties.</summary>
                    [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
                    [global::System.Obsolete("This field is not intended to be referenced directly by user code")]
                    public static readonly global::System.ComponentModel.PropertyChangingEventArgs LastName = new global::System.ComponentModel.PropertyChangingEventArgs("LastName");
                }
            }
            """;

        string changedArgs = """
            // <auto-generated/>
            #pragma warning disable
            #nullable enable
            namespace CommunityToolkit.Mvvm.ComponentModel.__Internals
            {
                /// <summary>
                /// A helper type providing cached, reusable <see cref="global::System.ComponentModel.PropertyChangedEventArgs"/> instances
                /// for all properties generated with <see cref="global::CommunityToolkit.Mvvm.ComponentModel.ObservablePropertyAttribute"/>.
                /// </summary>
                [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                [global::System.Diagnostics.DebuggerNonUserCode]
                [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
                [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
                [global::System.Obsolete("This type is not intended to be used directly by user code")]
                internal static class __KnownINotifyPropertyChangedArgs
                {
                    /// <summary>The cached <see cref="global::System.ComponentModel.PropertyChangedEventArgs"/> instance for all "FirstName" generated properties.</summary>
                    [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
                    [global::System.Obsolete("This field is not intended to be referenced directly by user code")]
                    public static readonly global::System.ComponentModel.PropertyChangedEventArgs FirstName = new global::System.ComponentModel.PropertyChangedEventArgs("FirstName");
                    /// <summary>The cached <see cref="global::System.ComponentModel.PropertyChangedEventArgs"/> instance for all "FullName" generated properties.</summary>
                    [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
                    [global::System.Obsolete("This field is not intended to be referenced directly by user code")]
                    public static readonly global::System.ComponentModel.PropertyChangedEventArgs FullName = new global::System.ComponentModel.PropertyChangedEventArgs("FullName");
                    /// <summary>The cached <see cref="global::System.ComponentModel.PropertyChangedEventArgs"/> instance for all "LastName" generated properties.</summary>
                    [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
                    [global::System.Obsolete("This field is not intended to be referenced directly by user code")]
                    public static readonly global::System.ComponentModel.PropertyChangedEventArgs LastName = new global::System.ComponentModel.PropertyChangedEventArgs("LastName");
                }
            }
            """;

        VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result), ("__KnownINotifyPropertyChangingArgs.g.cs", changingArgs), ("__KnownINotifyPropertyChangedArgs.g.cs", changedArgs));
    }

    // See https://github.com/CommunityToolkit/dotnet/issues/711
    [TestMethod]
    public void ObservableProperty_NotifyPropertyChangedFor_WithoutNotifyPropertyChanging()
    {
        string source = """
            using System;
            using CommunityToolkit.Mvvm.ComponentModel;

            #nullable enable

            namespace MyApp;

            abstract class BaseViewModel
            {
            }
            
            [INotifyPropertyChanged(IncludeAdditionalHelperMethods = false)]
            partial class MyViewModel : BaseViewModel
            {
                [ObservableProperty]
                [NotifyPropertyChangedFor(nameof(FullName))]
                private int firstName;

                [ObservableProperty]
                [NotifyPropertyChangedFor(nameof(FullName))]
                private int lastName;

                public string FullName => "";
            }
            """;

        string result = """
            // <auto-generated/>
            #pragma warning disable
            #nullable enable
            namespace MyApp
            {
                /// <inheritdoc/>
                partial class MyViewModel
                {
                    /// <inheritdoc cref="firstName"/>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
                    [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
                    public int FirstName
                    {
                        get => firstName;
                        set
                        {
                            if (!global::System.Collections.Generic.EqualityComparer<int>.Default.Equals(firstName, value))
                            {
                                OnFirstNameChanging(value);
                                OnFirstNameChanging(default, value);
                                firstName = value;
                                OnFirstNameChanged(value);
                                OnFirstNameChanged(default, value);
                                OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.FirstName);
                                OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.FullName);
                            }
                        }
                    }

                    /// <inheritdoc cref="lastName"/>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
                    [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
                    public int LastName
                    {
                        get => lastName;
                        set
                        {
                            if (!global::System.Collections.Generic.EqualityComparer<int>.Default.Equals(lastName, value))
                            {
                                OnLastNameChanging(value);
                                OnLastNameChanging(default, value);
                                lastName = value;
                                OnLastNameChanged(value);
                                OnLastNameChanged(default, value);
                                OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.LastName);
                                OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.FullName);
                            }
                        }
                    }

                    /// <summary>Executes the logic for when <see cref="FirstName"/> is changing.</summary>
                    /// <param name="value">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="FirstName"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
                    partial void OnFirstNameChanging(int value);
                    /// <summary>Executes the logic for when <see cref="FirstName"/> is changing.</summary>
                    /// <param name="oldValue">The previous property value that is being replaced.</param>
                    /// <param name="newValue">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="FirstName"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
                    partial void OnFirstNameChanging(int oldValue, int newValue);
                    /// <summary>Executes the logic for when <see cref="FirstName"/> just changed.</summary>
                    /// <param name="value">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="FirstName"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
                    partial void OnFirstNameChanged(int value);
                    /// <summary>Executes the logic for when <see cref="FirstName"/> just changed.</summary>
                    /// <param name="oldValue">The previous property value that was replaced.</param>
                    /// <param name="newValue">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="FirstName"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
                    partial void OnFirstNameChanged(int oldValue, int newValue);
                    /// <summary>Executes the logic for when <see cref="LastName"/> is changing.</summary>
                    /// <param name="value">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="LastName"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
                    partial void OnLastNameChanging(int value);
                    /// <summary>Executes the logic for when <see cref="LastName"/> is changing.</summary>
                    /// <param name="oldValue">The previous property value that is being replaced.</param>
                    /// <param name="newValue">The new property value being set.</param>
                    /// <remarks>This method is invoked right before the value of <see cref="LastName"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
                    partial void OnLastNameChanging(int oldValue, int newValue);
                    /// <summary>Executes the logic for when <see cref="LastName"/> just changed.</summary>
                    /// <param name="value">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="LastName"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
                    partial void OnLastNameChanged(int value);
                    /// <summary>Executes the logic for when <see cref="LastName"/> just changed.</summary>
                    /// <param name="oldValue">The previous property value that was replaced.</param>
                    /// <param name="newValue">The new property value that was set.</param>
                    /// <remarks>This method is invoked right after the value of <see cref="LastName"/> is changed.</remarks>
                    [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
                    partial void OnLastNameChanged(int oldValue, int newValue);
                }
            }
            """;

        string changedArgs = """
            // <auto-generated/>
            #pragma warning disable
            #nullable enable
            namespace CommunityToolkit.Mvvm.ComponentModel.__Internals
            {
                /// <summary>
                /// A helper type providing cached, reusable <see cref="global::System.ComponentModel.PropertyChangedEventArgs"/> instances
                /// for all properties generated with <see cref="global::CommunityToolkit.Mvvm.ComponentModel.ObservablePropertyAttribute"/>.
                /// </summary>
                [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", <ASSEMBLY_VERSION>)]
                [global::System.Diagnostics.DebuggerNonUserCode]
                [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
                [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
                [global::System.Obsolete("This type is not intended to be used directly by user code")]
                internal static class __KnownINotifyPropertyChangedArgs
                {
                    /// <summary>The cached <see cref="global::System.ComponentModel.PropertyChangedEventArgs"/> instance for all "FirstName" generated properties.</summary>
                    [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
                    [global::System.Obsolete("This field is not intended to be referenced directly by user code")]
                    public static readonly global::System.ComponentModel.PropertyChangedEventArgs FirstName = new global::System.ComponentModel.PropertyChangedEventArgs("FirstName");
                    /// <summary>The cached <see cref="global::System.ComponentModel.PropertyChangedEventArgs"/> instance for all "FullName" generated properties.</summary>
                    [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
                    [global::System.Obsolete("This field is not intended to be referenced directly by user code")]
                    public static readonly global::System.ComponentModel.PropertyChangedEventArgs FullName = new global::System.ComponentModel.PropertyChangedEventArgs("FullName");
                    /// <summary>The cached <see cref="global::System.ComponentModel.PropertyChangedEventArgs"/> instance for all "LastName" generated properties.</summary>
                    [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
                    [global::System.Obsolete("This field is not intended to be referenced directly by user code")]
                    public static readonly global::System.ComponentModel.PropertyChangedEventArgs LastName = new global::System.ComponentModel.PropertyChangedEventArgs("LastName");
                }
            }
            """;

        VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result), ("__KnownINotifyPropertyChangingArgs.g.cs", null), ("__KnownINotifyPropertyChangedArgs.g.cs", changedArgs));
    }

    /// <summary>
    /// Generates the requested sources
    /// </summary>
    /// <param name="source">The input source to process.</param>
    /// <param name="generators">The generators to apply to the input syntax tree.</param>
    /// <param name="results">The source files to compare.</param>
    private static void VerifyGenerateSources(string source, IIncrementalGenerator[] generators, params (string Filename, string? Text)[] results)
    {
        VerifyGenerateSources(source, generators, LanguageVersion.CSharp10, results);
    }

    /// <summary>
    /// Generates the requested sources
    /// </summary>
    /// <param name="source">The input source to process.</param>
    /// <param name="generators">The generators to apply to the input syntax tree.</param>
    /// <param name="languageVersion">The language version to use.</param>
    /// <param name="results">The source files to compare.</param>
    private static void VerifyGenerateSources(string source, IIncrementalGenerator[] generators, LanguageVersion languageVersion, params (string Filename, string? Text)[] results)
    {
        // Ensure CommunityToolkit.Mvvm and System.ComponentModel.DataAnnotations are loaded
        Type observableObjectType = typeof(ObservableObject);
        Type validationAttributeType = typeof(ValidationAttribute);

        // Get all assembly references for the loaded assemblies (easy way to pull in all necessary dependencies)
        IEnumerable<MetadataReference> references =
            from assembly in AppDomain.CurrentDomain.GetAssemblies()
            where !assembly.IsDynamic
            let reference = MetadataReference.CreateFromFile(assembly.Location)
            select reference;

        SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(source, CSharpParseOptions.Default.WithLanguageVersion(languageVersion));

        // Create a syntax tree with the input source
        CSharpCompilation compilation = CSharpCompilation.Create(
            "original",
            new SyntaxTree[] { syntaxTree },
            references,
            new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));

        GeneratorDriver driver = CSharpGeneratorDriver.Create(generators).WithUpdatedParseOptions((CSharpParseOptions)syntaxTree.Options);

        // Run all source generators on the input source code
        _ = driver.RunGeneratorsAndUpdateCompilation(compilation, out Compilation outputCompilation, out ImmutableArray<Diagnostic> diagnostics);

        // Ensure that no diagnostics were generated
        CollectionAssert.AreEquivalent(Array.Empty<Diagnostic>(), diagnostics);

        foreach ((string filename, string? text) in results)
        {
            if (text is not null)
            {
                string filePath = filename;

                // Update the assembly version using the version from the assembly of the input generators.
                // This allows the tests to not need updates whenever the version of the MVVM Toolkit changes.
                string expectedText = text.Replace("<ASSEMBLY_VERSION>", $"\"{generators[0].GetType().Assembly.GetName().Version}\"");

#if !ROSLYN_4_3_1_OR_GREATER
                // Adjust the filenames for the legacy Roslyn 4.0
                filePath = filePath.Replace('`', '_');
#endif

                SyntaxTree generatedTree = outputCompilation.SyntaxTrees.Single(tree => Path.GetFileName(tree.FilePath) == filePath);

                Assert.AreEqual(expectedText, generatedTree.ToString());
            }
            else
            {
                // If the text is null, verify that the file was not generated at all
                Assert.IsFalse(outputCompilation.SyntaxTrees.Any(tree => Path.GetFileName(tree.FilePath) == filename));
            }
        }

        GC.KeepAlive(observableObjectType);
        GC.KeepAlive(validationAttributeType);
    }
}
